/*
\copyright  Copyright (c) 2023 Qualcomm Technologies International, Ltd.
            All Rights Reserved.
            Qualcomm Technologies International, Ltd. Confidential and Proprietary.
\version    
\file       peer_sig_contiguous_data.c
\brief      Synchronises the publishing of an event, together with any event data, on the event bus
*/

#include "peer_sig_contiguous_data.h"
#include "event_bus_sniffer.h"
#include "sync_event.h"

#include <panic.h>
#include <logging.h>
#include <sink.h>
#include <vm.h>

#define DEBUG_LOG_SYNC_EVENTS DEBUG_LOG_ERROR

typedef enum {
    SYNC_EVENT_INTERNAL_MESSAGE = INTERNAL_MESSAGE_BASE
}sync_event_messsage_t;

static void syncEvent_MessageHandler(Task task, MessageId id, Message message);
static const TaskData peer_sig_notification_task = {.handler = syncEvent_MessageHandler};
static event_bus_sniffer_handler_t publish_sync_event;
static Sink sync_events_sink;

typedef struct
{
    event_bus_channel_t channel;
    event_bus_event_t event;
    
    uint16 data_len;
    /* Unused variable used to ensure data is 32 bit aligned */
    uint32 align;
    uint8 data[1];
} sync_event_t;

typedef struct
{
    rtime_t time_to_play;
    sync_event_t sync_event;
}TIMED_SYNC_EVENT_MSG_T;

static bool syncEvent_LocalToWallClock(rtime_t *wallclock_time)
{
    PanicNull(wallclock_time);
    wallclock_state_t wc_state;
    *wallclock_time = 0;
    if(!(SinkIsValid(sync_events_sink) &&
        RtimeWallClockGetStateForSink(&wc_state, sync_events_sink) &&
        RtimeLocalToWallClock(&wc_state, VmGetTimerTime(), wallclock_time)))
    {
        DEBUG_LOG_ERROR("syncEvent_LocalToWallClock Invalid wallclock");
    }
    return *wallclock_time ? TRUE : FALSE;
}

static bool syncEvent_WallClockToLocal(rtime_t wallclock_ttp, rtime_t *local_time)
{
    PanicNull(local_time);
    wallclock_state_t wc_state;
    *local_time = 0;
    if(!(wallclock_ttp &&
        SinkIsValid(sync_events_sink) &&
        RtimeWallClockGetStateForSink(&wc_state, sync_events_sink) &&
        RtimeWallClockToLocal(&wc_state, wallclock_ttp, local_time)))
    {        
        DEBUG_LOG_ERROR("syncEvent_WallClockToLocal Invalid wallclock");
    }
    return *local_time ? TRUE : FALSE;
}

static void syncEvent_ProcessRxdTimedEventMessage(PEER_SIG_MARSHALLED_MSG_CHANNEL_RX_IND_T* message)
{
    uint16 len = PeerSigContiguousData_GetDataLength(message);
    TIMED_SYNC_EVENT_MSG_T* timed_sync_event_msg = PanicUnlessMalloc(len);
    PeerSigContiguousData_GetReceivedData(message, timed_sync_event_msg);
    
    rtime_t time_to_play = timed_sync_event_msg->time_to_play;
    rtime_t local_ttp = 0;
    Delay delay_time = D_IMMEDIATE;
    if(syncEvent_WallClockToLocal(time_to_play, &local_ttp))
    {
        delay_time = RtimeTimeToMsDelay(local_ttp);
        DEBUG_LOG_V_VERBOSE("syncEvent_ProcessRxdMessage: PEER_SIG_MARSHALLED_MSG_CHANNEL_RX_IND rx ttp:%u local ttp:%u delay:%dmS", time_to_play, local_ttp, delay_time);
    }
    MessageSendLater((Task)(&peer_sig_notification_task), SYNC_EVENT_INTERNAL_MESSAGE, timed_sync_event_msg, delay_time);
    DEBUG_LOG_SYNC_EVENTS("syncEvent_ProcessRxdMessage channel=%d event=%d data_size=%u", timed_sync_event_msg->sync_event.channel, timed_sync_event_msg->sync_event.event, timed_sync_event_msg->sync_event.data_len);
}


static void inline syncEvent_ProcessInternalEventMessage(TIMED_SYNC_EVENT_MSG_T* timed_sync_event_msg)
{
    DEBUG_LOG_SYNC_EVENTS("syncEvent_ProcessInternalEventMessage");
    sync_event_t* sync_event = &timed_sync_event_msg->sync_event;
    publish_sync_event(sync_event->channel, sync_event->event, sync_event->data, sync_event->data_len);   
}

static void syncEvent_MessageHandler(Task task, MessageId id, Message message)
{
    UNUSED(task);

    switch (id)
    {
        case SYNC_EVENT_INTERNAL_MESSAGE:
            syncEvent_ProcessInternalEventMessage((TIMED_SYNC_EVENT_MSG_T*)message);
        break;

        case PEER_SIG_CONNECTION_IND:
            DEBUG_LOG_SYNC_EVENTS("syncEvent_MessageHandler: PEER_SIG_CONNECTION_IND");
        break;

        case PEER_SIG_MARSHALLED_MSG_CHANNEL_RX_IND:
            syncEvent_ProcessRxdTimedEventMessage((PEER_SIG_MARSHALLED_MSG_CHANNEL_RX_IND_T*)message);
        break;

        case PEER_SIG_MARSHALLED_MSG_CHANNEL_TX_CFM:
            DEBUG_LOG_SYNC_EVENTS("syncEvent_MessageHandler: PEER_SIG_MARSHALLED_MSG_CHANNEL_TX_CFM");
        break;

        default:
        break;
    }
}

static inline TIMED_SYNC_EVENT_MSG_T* syncEvent_CreateTimedSyncEventMsg(event_bus_channel_t channel, event_bus_event_t event, uint32 total_size, void * data, uint16 data_size)
{
    TIMED_SYNC_EVENT_MSG_T* timed_sync_event_msg = PanicUnlessMalloc(total_size);
    timed_sync_event_msg->time_to_play = 0;
    
    sync_event_t* sync_event = &timed_sync_event_msg->sync_event;    
    sync_event->channel = channel;
    sync_event->event = event;
    sync_event->data_len = data_size;
    memcpy(sync_event->data, data, data_size);
    return timed_sync_event_msg;
}

static inline void syncEvent_SetMsgTimes(uint32 *msg_send_delay, rtime_t *time_to_play)
{
    rtime_t current_wallclock_time;
    *msg_send_delay = 0;
    if(syncEvent_LocalToWallClock(&current_wallclock_time))
    {
        *msg_send_delay = appPeerGetPeerRelayDelayBasedOnSystemContext();
        *time_to_play = rtime_add(current_wallclock_time, *msg_send_delay);
    }
    DEBUG_LOG_V_VERBOSE("syncEvent_SetMsgTimes  relay time:%u ttp:%u", *msg_send_delay, *time_to_play);    
}

static void syncEvent_EventBusSyncEventHandler(event_bus_channel_t channel, event_bus_event_t event, void * data, uint16 data_size)
{
    uint32 total_size = sizeof(TIMED_SYNC_EVENT_MSG_T)+data_size;
    TIMED_SYNC_EVENT_MSG_T* timed_sync_event_msg = syncEvent_CreateTimedSyncEventMsg(channel, event, total_size, data, data_size);

    if(appPeerSigIsConnected())
    {        
        uint32 send_to_self_delay_uS;
        syncEvent_SetMsgTimes(&send_to_self_delay_uS, &timed_sync_event_msg->time_to_play);
        PeerSigContiguousData_Transmit((Task)(&peer_sig_notification_task), PEER_SIG_MSG_EVENT_SYNC, timed_sync_event_msg, total_size);
        MessageSendLater((Task)(&peer_sig_notification_task), SYNC_EVENT_INTERNAL_MESSAGE, timed_sync_event_msg, send_to_self_delay_uS/1000);
        DEBUG_LOG_SYNC_EVENTS("syncEvent_EventBusSyncEventHandler channel=%d event=%d data_size=%u", channel, event, data_size);        
    }
    else
    {
        MessageSend((Task)(&peer_sig_notification_task), SYNC_EVENT_INTERNAL_MESSAGE, timed_sync_event_msg);
        DEBUG_LOG_WARN("syncEvent_EventBusSyncEventHandler peer not connected, sending immediately");        
    }
}

bool SyncEvent_Init(Task init_task)
{
    UNUSED(init_task);
    EventBus_RegisterEventSniffer(syncEvent_EventBusSyncEventHandler, &publish_sync_event);
    PeerSigContiguousData_Register((Task)&peer_sig_notification_task, PEER_SIG_MSG_EVENT_SYNC);
    return TRUE;
}

void SyncEvent_SetWallClockSink(Sink sink)
{
    sync_events_sink = sink;
}

void SyncEvent_SubscribeSyncEvents(event_bus_channel_t channel, const event_bus_event_t sync_events[], uint8 num_sync_events, event_bus_subscriber_t subscriber)
{
    EventBus_SubscribeSnifferEvents(channel, sync_events, num_sync_events, subscriber);
}

void SyncEvent_UnsubscribeSyncEvents(event_bus_channel_t channel, const event_bus_event_t sync_events[], uint8 num_sync_events, event_bus_subscriber_t subscriber)
{
    // TODO
    UNUSED(sync_events);
    UNUSED(num_sync_events);
    EventBus_Unsubscribe(channel, subscriber);
}
